package com.sneakxy.mybase4j.generator;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.sneakxy.mybase4j.generator.config.Config;
import com.sneakxy.mybase4j.generator.config.GeneratorType;
import com.sneakxy.mybase4j.generator.config.Jdbc;
import com.sneakxy.mybase4j.generator.core.ClassDesigner;
import com.sneakxy.mybase4j.generator.core.ClassField;
import com.sneakxy.mybase4j.generator.core.ClassType;
import com.sneakxy.mybase4j.generator.jdbc.Dialect;
import com.sneakxy.mybase4j.generator.jdbc.JdbcHelper;
import com.sneakxy.mybase4j.generator.utils.JAXBUtils;
import com.sneakxy.mybase4j.generator.utils.StringExtUtils;

/**
 * 代码生成器
 * @author 潜行的青衣
 * @see
 */
public class Generator {
	
	private static Logger log = LogManager.getLogger(Generator.class);
	
	/**
	 * 配置文件
	 */
	private Config config;
	
	private JdbcHelper helper;
	
	private GeneratorPlugin plugin;
	
	
	
	public Generator() throws IOException {
		this(Generator.class.getResourceAsStream("/generator-config.xml"));
	}
	
	public Generator(String path) throws IOException {
		this(new FileInputStream(path));
	}
	
	public Generator(InputStream is) throws IOException {
		String xml = IOUtils.toString(is, Charset.forName("UTF-8"));
		IOUtils.closeQuietly(is);
		this.config = JAXBUtils.fromXML(xml, Config.class);
		if(this.config == null) {
			throw new NullPointerException("the file 'generator-config.xml' is not exist!");
		}
		this.init();
	}
	
	public Generator(Config config) {
		this.config = config;
		if(this.config == null) {
			throw new NullPointerException("config could not be null!");
		}
		this.init();
	}

	private void init() {
		this.helper = this.config.getJdbc().createJdbcHelper();
		if(StringUtils.isNotBlank(this.config.getPlugin())) {
			try {
				this.plugin = (GeneratorPlugin) Class.forName(this.config.getPlugin()).newInstance();
			} catch (InstantiationException e) {
				log.error("plugin could not be created.", e);
			} catch (IllegalAccessException e) {
				log.error("plugin could not be created.", e);
			} catch (ClassNotFoundException e) {
				log.error("plugin could not be created.", e);
			}
		}
	}
	
	/**
	 * 开始生成
	 * @author 潜行的青衣
	 * @version
	 * @throws FileNotFoundException 
	 */
	public void start() throws FileNotFoundException {
		Jdbc jdbc = this.getConfig().getJdbc();
		Dialect dialect = jdbc.createDialect();
		dialect.setUrl(jdbc.getUrl());
		try {
			helper.openConnection();
			//只有一个表
			if(StringUtils.isNotBlank(this.config.getGenerator().getTable())) {
				this.dealWithTable(dialect, this.config.getGenerator().getTable());
			} else {
				List<String> tables = this.getTables(dialect);
				Iterator<String> it = tables.iterator();
				while(it.hasNext()) {
					this.dealWithTable(dialect, it.next());
				}
			}
			helper.close();
		} catch (ClassNotFoundException e) {
			log.error("驱动加载失败.", e);
		} catch (SQLException e) {
			log.error("数据库操作失败.", e);
		}
	}
	
	private List<String> getTables(Dialect dialect) throws SQLException {
		List<String> result = helper.execute(dialect.getTablesSql(), new JdbcHelper.ResultDealable<String>() {

			@Override
			public List<String> onResultSetCallback(ResultSet rs) throws SQLException {
				List<String> result = new ArrayList<String>(); 
				while(rs.next()) {
					result.add(rs.getString(1));
				}
				return result;
			}
			
		});
		return result;
	}
	
	private void dealWithTable(Dialect dialect, String tableName) throws SQLException, FileNotFoundException {
		//是否存在插件
		boolean hasPlugin = this.plugin != null;
		List<ClassField> otherFields = this.getFields(dialect, tableName);
		List<ClassField> primaryKeys = this.getPrimaryKeyFields(dialect, tableName, otherFields);
		String modelName = this.getModelName(tableName);
		//model 设计器
		ClassDesigner model = this.buildModelDesigner(modelName, primaryKeys, otherFields);
		//Repository设计器
		ClassDesigner repository = buildRepositoryDesigner(model);
		//ProviderInterface设计器
		ClassDesigner providerInterface = buildProviderInterfaceDesigner(model);
		//ProviderImpl设计器
		ClassDesigner providerImpl = buildProviderImplDesigner(model);
		//ServiceInterface设计器
		ClassDesigner serviceInterface = buildServiceInterfaceDesigner(model);
		//ServiceImpl设计器
		ClassDesigner serviceImpl = buildServiceImplDesigner(model);
		//构建器
		GeneratorBuilder builder = new GeneratorBuilder(
				this.config.getDirs(), tableName, modelName, primaryKeys, otherFields,
				model, repository, serviceInterface, serviceImpl, providerInterface, providerImpl);
		if(hasPlugin) {
			this.plugin.onBeforeBuilderCode(builder, this.config.getGenerator());
		}
		builder.build();
		if(hasPlugin) {
			this.plugin.onAfterBuilderCode(builder, this.config.getGenerator());
		}
		log.info(MessageFormat.format("{0} - {1} - success", tableName, model.getTypeName()));
	}
	
	private List<ClassField> getFields(Dialect dialect, String tableName) throws SQLException {
		String sql = dialect.getSelectNone(tableName);
		List<ClassField> result = helper.execute(sql, new JdbcHelper.ResultDealable<ClassField>() {

			@Override
			public List<ClassField> onResultSetCallback(ResultSet rs) throws SQLException {
				ResultSetMetaData meta = rs.getMetaData();
				return getFields(meta);
			}
			
		});
		return result;
	}
	
	private List<String> getPrimaryKeyName(Dialect dialect, String tableName) throws SQLException {
		List<String> primaryKeyNames = new ArrayList<String>();
		ResultSet primaryKey = helper.getPrimaryKey(dialect.getCatalog(), dialect.getSchema(), tableName);
		while(primaryKey.next()) {
			primaryKeyNames.add(primaryKey.getString("COLUMN_NAME"));
		}
		return primaryKeyNames;
	}
	
	private List<ClassField> getPrimaryKeyFields(Dialect dialect, String tableName, List<ClassField> fields) throws SQLException {
		List<String> pkNames = getPrimaryKeyName(dialect, tableName);
		List<ClassField> result = new ArrayList<ClassField>();
		Iterator<ClassField> it = fields.iterator();
		ClassField field = null;
		while(it.hasNext()) {
			field = it.next();
			if(pkNames.contains(field.getExtra())) {
				result.add(field);
				it.remove();
			}
		}
		return result;
	}
	
	private List<ClassField> getFields(ResultSetMetaData meta) throws SQLException {
		List<ClassField> result = new ArrayList<ClassField>();
		int count = meta.getColumnCount() + 1;
		String columnName = null;
		int columnType = 0;
		String fieldName = null;
		ClassField field = null;
		for(int i=1; i<count; i++) {
			columnType = meta.getColumnType(i);
			columnName = StringUtils.lowerCase(meta.getColumnName(i));
			fieldName =  StringExtUtils.lowerCaseFirstChar(StringExtUtils.formatStringName(columnName));
			field = new ClassField(new ClassType(JdbcHelper.getJavaType(columnType)), fieldName);
			field.setExtra(columnName);
			field.setExtraType(columnType);
			result.add(field);
		}
		return result;
	}
	
	private ClassDesigner buildProviderImplDesigner(ClassDesigner model) {
		GeneratorType type = this.config.getGenerator().getProviderImpl();
		String fullName = buildClassFullName(model, type, "", "Provider");
		ClassDesigner designer = new ClassDesigner(fullName);
		return designer;
	}
	
	private ClassDesigner buildProviderInterfaceDesigner(ClassDesigner model) {
		GeneratorType type = this.config.getGenerator().getProviderInterface();
		String fullName = buildClassFullName(model, type, "I", "Provider");
		ClassDesigner designer = new ClassDesigner(fullName);
		designer.setInterfaceClass(true);
		return designer;
	}
	
	private ClassDesigner buildServiceImplDesigner(ClassDesigner model) {
		GeneratorType type = this.config.getGenerator().getServiceImpl();
		String fullName = buildClassFullName(model, type, "", "Service");
		ClassDesigner designer = new ClassDesigner(fullName);
		return designer;
	}
	
	private ClassDesigner buildServiceInterfaceDesigner(ClassDesigner model) {
		GeneratorType type = this.config.getGenerator().getServiceInterface();
		String fullName = buildClassFullName(model, type, "I", "Service");
		ClassDesigner designer = new ClassDesigner(fullName);
		designer.setInterfaceClass(true);
		return designer;
	}
	
	private ClassDesigner buildRepositoryDesigner(ClassDesigner model) {
		GeneratorType type = this.config.getGenerator().getRepository();
		String fullName = buildClassFullName(model, type, "", "Repository");
		ClassDesigner designer = new ClassDesigner(fullName);
		designer.setInterfaceClass(true);
		return designer;
	}
	
	private String buildClassFullName(ClassDesigner model, GeneratorType type, String prefix, String suffix) {
		StringBuilder s = new StringBuilder();
		s.append(this.config.getGenerator().getBasePackage());
		if(type != null && StringUtils.isNotBlank(type.getPackageName())) {
			if(!StringUtils.endsWith(this.config.getGenerator().getBasePackage(), ".")) {
				s.append(".");
			}
			s.append(type.getPackageName());
		}
		s.append(".");
		if(StringUtils.isNotBlank(type.getName())) {
			s.append(StringUtils.replace(type.getName(), "{modelName}", model.getTypeName()));
		} else {
			s.append(prefix).append(model.getTypeName()).append(suffix);
		}
		return s.toString();
	}
	
	private String getModelName(String tableName) {
		String modelName = this.config.getGenerator().getModelName();
		if(StringUtils.isNotBlank(modelName)) {
			return StringUtils.trimToEmpty(modelName);
		}
		return StringExtUtils.formatStringName(tableName);
	}
	
	private ClassDesigner buildModelDesigner(String modelName, List<ClassField> primaryKeys, List<ClassField> fields) throws SQLException {
		StringBuilder s = new StringBuilder();
		s.append(this.config.getGenerator().getBasePackage());
		GeneratorType type = this.config.getGenerator().getModel();
		if(type != null && StringUtils.isNotBlank(type.getPackageName())) {
			if(!StringUtils.endsWith(this.config.getGenerator().getBasePackage(), ".")) {
				s.append(".");
			}
			s.append(type.getPackageName());
		}
		s.append(".").append(modelName);
		ClassDesigner designer = new ClassDesigner(s.toString());
		designer.addClassField(primaryKeys);
		designer.addClassField(fields);
		return designer;
	}
	
	public Config getConfig() {
		return config;
	}

	public GeneratorPlugin getPlugin() {
		return plugin;
	}

	public void setPlugin(GeneratorPlugin plugin) {
		this.plugin = plugin;
	}
	
	public static void main(String[] args) throws FileNotFoundException, IOException {
		new Generator().start();
	}
	
}
